private import swift
private import DataFlowPrivate
private import TaintTrackingPublic
private import codeql.swift.dataflow.DataFlow
private import codeql.swift.dataflow.FlowSteps
private import codeql.swift.dataflow.Ssa
private import codeql.swift.controlflow.CfgNodes
private import FlowSummaryImpl as FlowSummaryImpl

/**
 * Holds if `node` should be a sanitizer in all global taint flow configurations
 * but not in local taint.
 */
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }

cached
private module Cached {
  /**
   * Holds if the additional step from `nodeFrom` to `nodeTo` should be included
   * in all global taint flow configurations.
   */
  cached
  predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
    (
      // Flow through one argument of `appendLiteral` and `appendInterpolation` and to the second argument.
      // This is needed for string interpolation generated by the compiler. An interpolated string
      // like `"I am \(n) years old."` is represented as
      // ```
      // $interpolated = ""
      // appendLiteral(&$interpolated, "I am ")
      // appendInterpolation(&$interpolated, n)
      // appendLiteral(&$interpolated, " years old.")
      // ```
      exists(ApplyExpr apply, ExprCfgNode e |
        nodeFrom.asExpr() = [apply.getAnArgument().getExpr(), apply.getQualifier()] and
        apply.getStaticTarget().getName() = ["appendLiteral(_:)", "appendInterpolation(_:)"] and
        e.getExpr() = apply.getQualifier() and
        nodeTo.(PostUpdateNodeImpl).getPreUpdateNode().getCfgNode() = e
      )
      or
      // Flow from the computation of the interpolated string literal to the result of the interpolation.
      exists(InterpolatedStringLiteralExpr interpolated |
        nodeTo.asExpr() = interpolated and
        nodeFrom.asExpr() = interpolated.getAppendingExpr()
      )
      or
      // allow flow through arithmetic (this case includes string concatenation)
      nodeTo.asExpr().(ArithmeticOperation).getAnOperand() = nodeFrom.asExpr()
      or
      // allow flow through bitwise operations
      nodeTo.asExpr().(BitwiseOperation).getAnOperand() = nodeFrom.asExpr()
      or
      // allow flow through assignment operations (e.g. `+=`)
      exists(AssignOperation op |
        nodeFrom.asExpr() = op.getSource() and
        nodeTo.asExpr() = op.getDest()
      )
      or
      // flow through a subscript access
      exists(SubscriptExpr se |
        se.getBase() = nodeFrom.asExpr() and
        se = nodeTo.asExpr()
      )
      or
      // flow through autoclosure expressions (which turn value arguments into closure arguments);
      // if the value is tainted, it's helpful to consider the autoclosure itself to be tainted as
      // well for the purposes of matching sink models.
      nodeFrom.asExpr() = nodeTo.asExpr().(AutoClosureExpr).getExpr()
      or
      // flow through the read of a content that inherits taint
      exists(DataFlow::ContentSet f |
        readStep(nodeFrom, f, nodeTo) and
        f.getAReadContent() instanceof TaintInheritingContent
      )
    ) and
    model = ""
    or
    // flow through a flow summary (extension of `SummaryModelCsv`)
    FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom.(FlowSummaryNode).getSummaryNode(),
      nodeTo.(FlowSummaryNode).getSummaryNode(), false, model)
    or
    any(AdditionalTaintStep a).step(nodeFrom, nodeTo) and model = "AdditionalTaintStep"
  }

  /**
   * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
   * (intra-procedural) step.
   */
  cached
  predicate localTaintStepCached(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
    DataFlow::localFlowStep(nodeFrom, nodeTo)
    or
    defaultAdditionalTaintStep(nodeFrom, nodeTo, _)
    or
    // Simple flow through library code is included in the exposed local
    // step relation, even though flow is technically inter-procedural
    FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
  }
}

import Cached
import SpeculativeTaintFlow

private module SpeculativeTaintFlow {
  private import codeql.swift.dataflow.internal.DataFlowDispatch as DataFlowDispatch
  private import codeql.swift.dataflow.internal.DataFlowPublic as DataFlowPublic
  private import codeql.swift.dataflow.internal.DataFlowPrivate as DataFlowPrivate

  /**
   * Holds if the additional step from `src` to `sink` should be considered in
   * speculative taint flow exploration.
   */
  predicate speculativeTaintStep(DataFlow::Node src, DataFlow::Node sink) {
    exists(DataFlowDispatch::DataFlowCall call, DataFlowDispatch::ArgumentPosition argpos |
      // TODO: exclude neutrals and anything that has QL modeling.
      not exists(DataFlowDispatch::viableCallable(call)) and
      src.(DataFlowPrivate::ArgumentNode).argumentOf(call, argpos)
    |
      not argpos instanceof DataFlowDispatch::ThisArgumentPosition and
      sink.(DataFlowPublic::PostUpdateNode)
          .getPreUpdateNode()
          .(DataFlowPrivate::ArgumentNode)
          .argumentOf(call,
            any(DataFlowDispatch::ArgumentPosition qualpos |
              qualpos instanceof DataFlowDispatch::ThisArgumentPosition
            ))
      or
      sink.(DataFlowPrivate::OutNode).getCall(_) = call
    )
  }
}
